package com.gzsxy.esjy.gateway.service.impl;

import com.alibaba.fastjson.JSON;
import com.gzsxy.esjy.bus.message.InnerProtocol;
import com.gzsxy.esjy.bus.message.MessageFactory;
import com.gzsxy.esjy.bus.service.LoginlogService;
import com.gzsxy.esjy.common.base.exception.impl.CustomAssert;
import com.gzsxy.esjy.common.base.result.ResultCodeEnum;
import com.gzsxy.esjy.common.base.util.Base64Util;
import com.gzsxy.esjy.common.base.util.RSAUtils;
import com.gzsxy.esjy.gateway.config.properties.GateWayProperties;
import com.gzsxy.esjy.gateway.dto.LoginDto;
import com.gzsxy.esjy.gateway.dto.LoginReadyResultDto;
import com.gzsxy.esjy.gateway.dto.MenuTreeDto;
import com.gzsxy.esjy.gateway.service.GlobalService;
import com.gzsxy.esjy.gateway.sso.SsoRestTemple;
import com.gzsxy.esjy.gateway.sso.dto.DefaultUser;
import com.gzsxy.esjy.gateway.sso.dto.MenuResult;
import com.gzsxy.esjy.gateway.sso.dto.SSOUserRequest;
import com.gzsxy.esjy.gateway.sso.dto.UserIdResult;
import com.gzsxy.esjy.redis.extend.RedisLock;
import com.gzsxy.esjy.redis.extend.RedisService;
import com.gzsxy.esjy.redis.extend.impl.Lock;
import com.gzsxy.esjy.redis.user.AuthContants;
import com.gzsxy.esjy.redis.user.ISystemUser;
import com.gzsxy.esjy.service.base.DictionaryConstants;
import com.gzsxy.esjy.service.base.dto.request.LoginLogRequest;
import com.gzsxy.esjy.service.base.dto.request.UserRequest;
import eu.bitwalker.useragentutils.Browser;
import eu.bitwalker.useragentutils.OperatingSystem;
import eu.bitwalker.useragentutils.UserAgent;
import eu.bitwalker.useragentutils.Version;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.http.HttpHeaders;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.server.ServerWebExchange;

import java.net.URI;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.UUID;

/**
 * @author xiaolong
 * @version 1.0
 * @description: TODO
 * @date 2021/11/15 9:47
 */
@Slf4j
@Service
@RefreshScope    //刷新配置文件
public class NewSsoServiceImpl implements GlobalService {

    private final static String LOGIN_ID = "_loginId";
    private final static String ACCESS_TOKEN = "_token";
    private final static String USER_PASSWORD = "_password";
    private final static String LOGIN_NAME = "_loginName";
    private final static String IS_REFRESH = "_fresreshed";

    /**
     * 预留60秒
     */
    private final static int LEAST_TIME = 60;



    /**
     * 获取token
     */
    private final static String REQUEST_TOKEN = "/oauth/token?grant_type=password&username=%s&password=%s&client_id=client_id&client_secret=client_secret";
    /**
     * 刷新token
     */
    private final static String REFRESH_TOKEN = "/oauth/token?grant_type=refresh_token&refresh_token=%s&client_id=client_id&client_secret=client_secret";
    /**
     * 注销token
     */
    private final static String LOGOUT = "/oauth/logout?access_token=";
    /**
     * 校验token
     */
    private final static String CHECKTOKEN = "/oauth/check_token?token=";

    /**
     * 获取菜单
     */
    private final static String MENU = "/api/v1/config/menu/getUserMenu?appCode=";

    /**
     * 新增用户
     */
    private final static String ADDUSER = "/api/v1/config/user/add";

    /**
     * 查询当前系统的所有商家用户
     */
    private final static String SELECTALLID = "/api/v1/config/user/getAllUserId";


    @Value("${Sso.core.url:}")
    private String url;

    @Autowired
    private RedisService redisService;

    @Autowired
    private SsoRestTemple ssoRestTemple;

    @Autowired
    private GateWayProperties properties;

    @Autowired
    private RedisLock redisLock;

    @Autowired
    private MessageFactory messageFactory;


    @Autowired
    private LoginlogService loginlogService;

    /**
     * @description: 登录准备
     * @return
     */
    @Override
    public LoginReadyResultDto loginReady(String username, String loginId) {
       if (StringUtils.isEmpty(loginId)){
           loginId = UUID.randomUUID().toString();
       }
        try {
            //生成RSA密钥对 （公钥加密，私钥解密，公钥发给客户端让其对密码加密，私钥用来解密）
            Map<String, Object> keyPair = RSAUtils.getKeyPair();
            //生成公钥
            String publicKey = RSAUtils.getPublicKey(keyPair);
            //生成私钥
            String privateKey = RSAUtils.getPrivateKey(keyPair);
            redisService.setString(loginId+LOGIN_ID,privateKey,3600);
            redisService.setString(loginId+LOGIN_NAME,username,3600);
            //返回公钥给客户端
            LoginReadyResultDto dto = new LoginReadyResultDto();
            dto.setEncodeKey(publicKey);
            dto.setLoginId(loginId);
            return dto;
        } catch (Exception e) {
            log.error(e.getMessage(),e);
        }
        return null;
    }


    /**
     * @description: 登录
     * @param
     * @return
     */
    @Override
    public DefaultUser login(LoginDto loginDto, ServerWebExchange exchange) {
        String loginId = loginDto.getLoginId();
        String privateKey = redisService.getString(loginId + LOGIN_ID);
        String username = redisService.getString(loginId + LOGIN_NAME);
        //通过私钥解密得到密码
        String str = RSAUtils.decryptByPrivateKey(loginDto.getPassword(), privateKey);
        String password = Base64Util.encode(Base64Util.encryptToMD5(str));
        return login(username,password,loginId,exchange);
    }

    /**
     * @description: 直接登录
     * @param
     * @return
     */
    @Override
    public DefaultUser login(String username, String password, String loginId, ServerWebExchange exchange) {
        DefaultUser user = ssoRestTemple.get(URI.create(url + String.format(REQUEST_TOKEN, username, password)).toString(), DefaultUser.class, getIpAddress(exchange.getRequest()));
        //当loginId不存在
        if (StringUtils.isEmpty(loginId)){
            loginId = UUID.randomUUID().toString();
        }
        //当用户还有1分钟超时
        if (user.getExpiresIn() < 2*LEAST_TIME){
            logout(user.getAccessToken());
            return login(username,password,loginId,exchange);
        }
        user.setClientId(loginId);
        //设置头部token
        user.setAuthorization(user.getTokenType()+" "+user.getAccessToken());
        //截至时间 token
        user.setExpiresBy(System.currentTimeMillis() / 1000 +user.getExpiresIn()-2*LEAST_TIME);
        redisService.setValue(user.getAccessToken() + ACCESS_TOKEN,user,user.getExpiresIn()-2*LEAST_TIME);

        //设置网关路由请求头
        exchange.getResponse().getHeaders().add(AuthContants.RESPONSE_REFRESH_TOKEN,user.getRefreshToken());
        exchange.getResponse().getHeaders().add(AuthContants.RESPONSE_TOKEN,user.getToken());

        try {
            //添加登录日志
            this.addLoginlog(username,exchange,user);
        } catch (Exception e) {
            log.error("前台登录日志入库失败：{}",e);
        }
        return user;
    }

    /**
     * @description: 登录日志
     * @param
     * @return
     */
    public void addLoginlog(String username,ServerWebExchange exchange,DefaultUser user){
        LoginLogRequest logRequest = new LoginLogRequest();
        logRequest.setUserName(username);
        HttpHeaders headers = exchange.getRequest().getHeaders();
        String first = headers.getFirst("User-Agent");
        UserAgent userAgent = UserAgent.parseUserAgentString(first);
        Browser browser = userAgent.getBrowser();
        String browserName = browser.getName();
        logRequest.setBrowserName(browserName);
        if (browserName.equals("Unknown")){
            logRequest.setVersion("Unknown");
            logRequest.setOsName("Unknown");
        } else {
            //获取浏览器版本号
            Version version = browser.getVersion(first);
            OperatingSystem operatingSystem = userAgent.getOperatingSystem();
            logRequest.setVersion(version.getVersion());
            logRequest.setOsName(operatingSystem.getName());
        }
        logRequest.setIp(getIpAddress(exchange.getRequest()));
        logRequest.setSchool("广州商学院");
        logRequest.setLoginTime(new Date());
        logRequest.setResponseParam(JSON.toJSONString(user));
        logRequest.setCreateTime(new Date());
        logRequest.setUpdateTime(new Date());
        loginlogService.addLoginlog(logRequest);
    }


    /**
     * @description: 获取登录用户
     * @param
     * @return
     */
    @Override
    public ISystemUser getLoginUser(String access_token, ServerWebExchange exchange) {
        //在redis缓存中读取
        DefaultUser user = redisService.getValue(access_token + ACCESS_TOKEN, DefaultUser.class);
        CustomAssert.notNull(user,ResultCodeEnum.FETCH_USERINFO_ERROR.getMessage(),ResultCodeEnum.FETCH_USERINFO_ERROR.getCode());
        //token剩余有效期
        long time = user.getExpiresBy() - System.currentTimeMillis() / 1000;
        //token 刷新预留时间
        long reserveMinute = properties.getAuth().getReserveMinute() * 60;
        // 浮动时间
        long floatMinute = properties.getAuth().getFloatMinute() * 60;
        log.info("token:{},剩余:{}:{}",access_token,time,reserveMinute);
        if (time >= reserveMinute){
            return user;
        }
        if (time <= floatMinute) {
            log.info("session 错过请求时间{}",access_token);
            return null;
        }
        return (ISystemUser)redisLock.excute(lock -> {
            //在redis缓存中读取
            DefaultUser userInner = redisService.getValue(access_token + ACCESS_TOKEN, DefaultUser.class);
            //token校验失败,用戶未登录
            CustomAssert.notNull(user,ResultCodeEnum.FETCH_USERINFO_ERROR.getMessage(),ResultCodeEnum.FETCH_USERINFO_ERROR.getCode());
            //token剩余有效期
            long timeInner = user.getExpiresBy() - System.currentTimeMillis() / 1000;
            //token 刷新预留时间
            long reserveMinuteInner = properties.getAuth().getReserveMinute() * 60;
            // 浮动时间
            long floatMinuteInner = properties.getAuth().getFloatMinute() * 60;
            if (timeInner >= reserveMinuteInner){
                return userInner;
            }
            if (timeInner <= floatMinuteInner){
                log.info("session 错过请求时间{}",access_token);
                return null;
            }
            //自动刷新token
            DefaultUser newUser = (DefaultUser) refresh(userInner.getAccessToken(), userInner.getRefreshToken(), exchange);
            exchange.getResponse().getHeaders().add(AuthContants.RESPONSE_TOKEN,newUser.getToken());
            log.info("得到最终token {}",newUser.getAccessToken());
            return newUser;
        },new Lock(user.getClientId()));
    }

    /**
     * @description: 退出登录
     * @param
     * @return
     */
    @Override
    public void logout(String access_token) {
        //token校验失败,用戶未登录
        CustomAssert.notBlank(access_token,ResultCodeEnum.FETCH_ACCESSTOKEN_FAILD.getMessage(),ResultCodeEnum.FETCH_ACCESSTOKEN_FAILD.getCode());
        ssoRestTemple.get(url+LOGOUT+access_token,DefaultUser.class,null);
        redisService.remove(access_token+ACCESS_TOKEN);
//        redisService.remove(access_token+USER_PASSWORD);

    }


    /**
     * @description: 刷新token
     * @param
     * @return
     */
    @Override
    public ISystemUser refresh(String access_token, String refresh_token, ServerWebExchange exchange) {

        //去除登录信息
        DefaultUser user = redisService.getValue(access_token + ACCESS_TOKEN, DefaultUser.class);
        //登录过期，请重新登录!
        CustomAssert.notNull(user,ResultCodeEnum.LOGIN_EXPIRED.getMessage(),ResultCodeEnum.LOGIN_EXPIRED.getCode());
        Object o = redisService.getValue(access_token + IS_REFRESH);
        if (!ObjectUtils.isEmpty(o)){
            log.info("已经刷新过 {},{}",access_token,refresh_token);
            //已经刷新过，返回之前的用户信息
            if (o instanceof String){
                user.setAccessToken((String)o);
            }
            return user;
        }
        log.info("刷新token {},{}",access_token,refresh_token);
        DefaultUser defaultUser = ssoRestTemple.get(URI.create(url + String.format(REFRESH_TOKEN, refresh_token)).toString(), DefaultUser.class, getIpAddress(exchange.getRequest()));
        //登录过期，请重新登录!
        CustomAssert.isBlank(defaultUser,ResultCodeEnum.LOGIN_EXPIRED.getMessage(),ResultCodeEnum.LOGIN_EXPIRED.getCode());
        log.info("刷新token成功 旧:{},新:{}",access_token,defaultUser.getAccessToken());
        //记录refresh已经使用
        redisService.setValue(access_token+IS_REFRESH,defaultUser.getRefreshToken(),user.getExpiresIn());
        defaultUser.setAuthorization(String.format("%s %s",defaultUser.getTokenType(),defaultUser.getAccessToken()));
        defaultUser.setExpiresBy(System.currentTimeMillis() /1000 +defaultUser.getExpiresIn()-LEAST_TIME);
        defaultUser.setClientId(user.getClientId());
        //存入刷新token后的用户信息
        redisService.setValue(defaultUser.getAccessToken()+ACCESS_TOKEN,defaultUser,defaultUser.getExpiresIn()-2*LEAST_TIME);
        return defaultUser;
    }


    /**
     * @description: 获取用户菜单
     * @param accessToken  token
     * @param appCode  应用code
     * @return
     */
    @Override
    public List<MenuTreeDto> getUserMenu(String accessToken, String appCode, ServerWebExchange exchange) {
        //appcode不存在
        CustomAssert.notBlank(appCode, ResultCodeEnum.APP_NON_EXISTENT.getMessage());
        ISystemUser loginUser = getLoginUser(accessToken, exchange);
        //token已过期
        CustomAssert.notTrue(ObjectUtils.isNotEmpty(loginUser),ResultCodeEnum.FETCH_TOKEN_FAILD.getMessage());
        List<MenuTreeDto> result = ssoRestTemple.getListByToken(url + MENU + appCode, loginUser.getToken(), MenuResult.class, MenuTreeDto.class);
        redisService.setValue("menu-"+loginUser.getUserAccout(),result,LEAST_TIME*LEAST_TIME*24*7);
        return result;
    }


    /**
     * @description: 商家审核成功注册账户
     * @param
     * @return
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public void verify(String accessToken, ServerWebExchange exchange,UserRequest userRequest) {
        ISystemUser loginUser = getLoginUser(accessToken, exchange);
        //token已过期
        CustomAssert.notTrue(ObjectUtils.isNotEmpty(loginUser),ResultCodeEnum.FETCH_TOKEN_FAILD.getMessage());
        //TODO 商家如果审核通过，会往权限认证系统添加当前用户被自定义分配商家角色
        if (DictionaryConstants.NUMBER_3.equals(userRequest.getFreeze())){
            SSOUserRequest ssoUser = new SSOUserRequest();
            BeanUtils.copyProperties(userRequest,ssoUser);
            ssoUser.setRealName(userRequest.getName());
            ssoRestTemple.post(url+ADDUSER,loginUser.getToken(),DefaultUser.class,JSON.toJSONString(ssoUser));
            //发送消息
            messageFactory.send("service-auth",new InnerProtocol().body(userRequest).cmd("/VERIFY_SUCCESS"));
        } else {
            messageFactory.send("service-auth",new InnerProtocol().body(userRequest).cmd("/VERIFY_ERROR"));
        }
    }

    /**
     * @description: 获取当前系统的所有商家id
     * @param
     * @return
     */
    @Override
    public void selectAllUserId() {
        UserIdResult idList = ssoRestTemple.get(url + SELECTALLID, UserIdResult.class, null);
        redisService.setValue(DictionaryConstants.USER_ID,idList.getData());
        log.info("获取当前系统的商家id列表如下：【{}】",idList.getData());
    }
}
